package com.dlg.commons.spring.fix.http;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Locale;

import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolException;
import org.apache.http.client.CircularRedirectException;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.impl.client.RedirectLocations;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 修复Post不重定向问题
 * 
 * @author martin_duan@yeah.net
 * @since 2016-06-02
 *
 */
public class FixPostRedirectStrategy implements RedirectStrategy {

	private final Logger logger = LoggerFactory.getLogger(getClass());

	public static final FixPostRedirectStrategy INSTANCE = new FixPostRedirectStrategy();

	private static final String[] REDIRECT_METHODS = new String[] { HttpGet.METHOD_NAME, HttpPost.METHOD_NAME, HttpHead.METHOD_NAME };

	public FixPostRedirectStrategy() {
	}

	public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException {
		final int statusCode = response.getStatusLine().getStatusCode();
		final String method = request.getRequestLine().getMethod();
		final Header locationHeader = response.getFirstHeader("location");
		switch (statusCode) {
		case HttpStatus.SC_MOVED_TEMPORARILY:
			return isRedirectable(method) && locationHeader != null;
		case HttpStatus.SC_MOVED_PERMANENTLY:
		case HttpStatus.SC_TEMPORARY_REDIRECT:
			return isRedirectable(method);
		case HttpStatus.SC_SEE_OTHER:
			return true;
		default:
			return false;
		}
	}

	public URI getLocationURI(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException {
		final HttpClientContext clientContext = HttpClientContext.adapt(context);
		final Header locationHeader = response.getFirstHeader("location");
		if (locationHeader == null) {
			throw new ProtocolException("Received redirect response " + response.getStatusLine() + " but no location header");
		}
		final String location = locationHeader.getValue();
		this.logger.debug("Redirect requested to location '{}'", location);
		final RequestConfig config = clientContext.getRequestConfig();
		URI uri = createLocationURI(location);
		try {
			if (!uri.isAbsolute()) {
				if (!config.isRelativeRedirectsAllowed()) {
					throw new ProtocolException("Relative redirect location '" + uri + "' not allowed");
				}
				final HttpHost target = clientContext.getTargetHost();
				final URI requestURI = new URI(request.getRequestLine().getUri());
				final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, false);
				uri = URIUtils.resolve(absoluteRequestURI, uri);
			}
		} catch (final URISyntaxException ex) {
			throw new ProtocolException(ex.getMessage(), ex);
		}

		RedirectLocations redirectLocations = (RedirectLocations) clientContext.getAttribute(HttpClientContext.REDIRECT_LOCATIONS);
		if (redirectLocations == null) {
			redirectLocations = new RedirectLocations();
			context.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, redirectLocations);
		}
		if (!config.isCircularRedirectsAllowed()) {
			if (redirectLocations.contains(uri)) {
				throw new CircularRedirectException("Circular redirect to '" + uri + "'");
			}
		}
		redirectLocations.add(uri);
		return uri;
	}

	protected URI createLocationURI(final String location) throws ProtocolException {
		try {
			final URIBuilder b = new URIBuilder(new URI(location).normalize());
			final String host = b.getHost();
			if (host != null) {
				b.setHost(host.toLowerCase(Locale.ENGLISH));
			}
			final String path = b.getPath();
			if (TextUtils.isEmpty(path)) {
				b.setPath("/");
			}
			return b.build();
		} catch (final URISyntaxException ex) {
			throw new ProtocolException("Invalid redirect URI: " + location, ex);
		}
	}

	protected boolean isRedirectable(final String method) {
		for (final String m : REDIRECT_METHODS) {
			if (m.equalsIgnoreCase(method)) {
				return true;
			}
		}
		return false;
	}

	public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException {
		final URI uri = getLocationURI(request, response, context);
		final String method = request.getRequestLine().getMethod();
		if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
			return new HttpHead(uri);
		} else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME) || method.equalsIgnoreCase(HttpPost.METHOD_NAME)) {
			return new HttpGet(uri);
		} else {
			final int status = response.getStatusLine().getStatusCode();
			if (status == HttpStatus.SC_TEMPORARY_REDIRECT) {
				return RequestBuilder.copy(request).setUri(uri).build();
			} else {
				return new HttpGet(uri);
			}
		}
	}

}
